package com.derbysoft.dswitch.adapter.ihg.dswitch2.service.impl;

import com.derbysoft.dswitch.adapter.ihg.common.domain.ChannelAuthorization;
import com.derbysoft.dswitch.adapter.ihg.common.domain.ReservationItem;
import com.derbysoft.dswitch.adapter.ihg.common.domain.ReservationModifyItem;
import com.derbysoft.dswitch.adapter.ihg.common.domain.Status;
import com.derbysoft.dswitch.adapter.ihg.common.log.LogBody;
import com.derbysoft.dswitch.adapter.ihg.common.log.PerformanceLogger;
import com.derbysoft.dswitch.adapter.ihg.common.repository.ResModifyItemRepository;
import com.derbysoft.dswitch.adapter.ihg.common.repository.ReservationRepository;
import com.derbysoft.dswitch.adapter.ihg.common.utils.CollectionUtil;
import com.derbysoft.dswitch.adapter.ihg.common.utils.DateTimeUtils;
import com.derbysoft.dswitch.adapter.ihg.common.utils.EnvironmentUtils;
import com.derbysoft.dswitch.adapter.ihg.dswitch2.HotelProviderService;
import com.derbysoft.dswitch.adapter.ihg.dswitch3.translate.Translator;
import com.derbysoft.dswitch.adapter.ihg.dto.Error;
import com.derbysoft.dswitch.adapter.ihg.dto.*;
import com.derbysoft.dswitch.adapter.ihg.webservice.handler.WebServiceHandler;
import com.derbysoft.dswitch.adapter.ihg.webservice.impl.AccountProvider;
import com.derbysoft.remote.dswitch.core.*;
import com.derbysoft.remote.dswitch.generic.constants.TransactionActionType;
import com.derbysoft.remote.dswitch.generic.constants.UniqueIDType;
import com.derbysoft.remote.dswitch.hotel.dto.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

@Service("ds2hotelReservationWebService")
public class HotelReservationWebServiceImpl extends BaseHotelWebService implements
        HotelProviderService<GenericRQ<HotelReservationRQ>, GenericRS<HotelReservationRS>> {

    @Autowired
    @Qualifier("ds2HotelPreviewRQTranslator")
    private Translator<GenericRQ<HotelReservationRQ>, OTAHotelAvailRQ, Void> hotelPreviewRQTranslator;

    @Autowired
    @Qualifier("ds2HotelPreviewRSTranslator")
    private Translator<OTAHotelAvailRS, GenericRS<HotelReservationRS>, GenericRQ<HotelReservationRQ>> hotelPreviewRSTranslator;

    @Autowired
    private Translator<GenericRQ<HotelReservationRQ>, OTAHotelResRQ, Void> hotelReservationRQTranslatorV2;

    @Autowired
    @Qualifier("availWebServiceHandler")
    private WebServiceHandler<OTAHotelAvailRQ, OTAHotelAvailRS> hotelAvailWebService;

    @Autowired
    @Qualifier("bookWebServiceHandler")
    private WebServiceHandler<OTAHotelResRQ, OTAHotelResRS> reservationWebService;

    @Autowired
    private ReservationRepository reservationRepository;

    @Autowired
    private ResModifyItemRepository resModifyItemRepository;

    @Override
    public GenericRS<HotelReservationRS> invoke(GenericRQ<HotelReservationRQ> request) {
        try {
            String erspNo = ReservationIDUtils.getErspNo(request.getPayload().getHotelReservationIDs()).getResIDValue();
            ChannelAuthorization authorization = loadAuthorization(request.getHeader().getSource().getUniqueID().getId());
            OTAHotelAvailRS otaHotelAvailRS = getAvailAbility(request, authorization);
            if ((otaHotelAvailRS.getErrors() != null && CollectionUtil.isNotEmpty(otaHotelAvailRS.getErrors().getError()))
                    || !findAvailRoomStay(otaHotelAvailRS, request.getPayload())) {
                return noAllotment(request, otaHotelAvailRS, erspNo);
            }

            saveOrUpdateReservation(request.getPayload().getResActionType(), erspNo, request.getPayload().getHotelReservation().getHotelRef().getCode());

            OTAHotelResRQ otaHotelResRQ = hotelReservationRQTranslatorV2.translate(request, null);

            OTAHotelResRS otaHotelResRS = reservationWebService.invoke(otaHotelResRQ, true,
                    AccountProvider.of(EnvironmentUtils.getIhgReservationUrl(), authorization));

            if (otaHotelResRS.getErrors() != null && CollectionUtil.isNotEmpty(otaHotelResRS.getErrors().getError())) {
                updateStatus(request.getPayload().getResActionType(), erspNo, false);
                return createFailedResponse(otaHotelResRS, request);
            }

            saveOrUpdateReservationItem(request, erspNo, getConfirmNumber(otaHotelResRS));

            GenericRS<HotelReservationRS> reservationRS = hotelPreviewRSTranslator.translate(otaHotelAvailRS, request);
            if (reservationRS.getSuccess() != null) {
                setConfirmNumber(reservationRS, otaHotelResRS);
            }
            updateStatus(request.getPayload().getResActionType(), erspNo, true);
            ReservationRoomStayDTO reservationRoomStayDTO = reservationRS.getPayload().getHotelReservation().getRoomStays().get(0);
            if (ReservationRateHelper.check(otaHotelAvailRS, otaHotelResRS, reservationRoomStayDTO.getRatePlanCode(), reservationRoomStayDTO.getRoomTypeCode())) {
                OTAHotelAvailRS otaHotelAvailRS2 = getAvailAbility(request, authorization);
                if (otaHotelAvailRS2.getSuccess() != null) {
                    ReservationRateHelper.check(otaHotelAvailRS2, otaHotelResRS, reservationRoomStayDTO.getRatePlanCode(), reservationRoomStayDTO.getRoomTypeCode());
                }
            }
            return reservationRS;
        } catch (Exception ex) {
            GenericRS<HotelReservationRS> failedResponse = createFailedResponse(ex, request);
            PerformanceLogger.append(LogBody.ERROR_CODE, failedResponse.getErrors().getErrorList().get(0).getCode());
            return failedResponse;
        }
    }

    private GenericRS<HotelReservationRS> createFailedResponse(Exception ex, GenericRQ<HotelReservationRQ> request) {
        GenericRS<HotelReservationRS> response = new GenericRS<HotelReservationRS>();
        ResponseHeader header = new ResponseHeader();
        header.setTaskId(request.getHeader().getTaskId());
        response.setHeader(header);
        response.setErrors(createErrorDTO(ex));
        return response;
    }

    private void saveOrUpdateReservationItem(GenericRQ<HotelReservationRQ> request, String erspNo, String confirmNumber) {
        ReservationRoomStayDTO reservationRoomStayDTO = request.getPayload().getHotelReservation().getRoomStays().get(0);
        if (request.getPayload().getResActionType() == TransactionActionType.BOOK) {
            reservationRepository.addReservationItem(createReservationItem(reservationRoomStayDTO, erspNo, confirmNumber));
        } else {
            resModifyItemRepository.save(createModifyItem(reservationRoomStayDTO, erspNo, confirmNumber));
        }
    }

    private boolean findAvailRoomStay(OTAHotelAvailRS otaHotelAvailRS, HotelReservationRQ request) {
        if (otaHotelAvailRS.getRoomStays() == null
                || CollectionUtil.isEmpty(otaHotelAvailRS.getRoomStays().getRoomStay())
                || otaHotelAvailRS.getRoomStays().getRoomStay().get(0) == null
                || otaHotelAvailRS.getRoomStays().getRoomStay().get(0).getRoomRates() == null
                || CollectionUtil.isEmpty(otaHotelAvailRS.getRoomStays().getRoomStay().get(0).getRoomRates().getRoomRate())) {
            return false;
        }
        for (RoomRate roomRate : otaHotelAvailRS.getRoomStays().getRoomStay().get(0).getRoomRates().getRoomRate()) {
            ReservationRoomStayDTO roomStayDTO = request.getHotelReservation().getRoomStays().get(0);
            if (roomRate.getRatePlanCode().equals(roomStayDTO.getRatePlanCode())
                    && roomRate.getRoomTypeCode().equals(roomStayDTO.getRoomTypeCode() + roomStayDTO.getRatePlanCode())) {
                return true;
            }
        }
        return false;
    }

    private ReservationItem createReservationItem(ReservationRoomStayDTO reservationRoomStayDTO, String erspNo, String confirmNumber) {
        StayDateRangeDTO stayDateRange = reservationRoomStayDTO.getStayDateRange();
        com.derbysoft.remote.dswitch.generic.dto.CustomerDTO customerDTO = reservationRoomStayDTO.getGuests().get(0);
        String givenName = customerDTO.getPersonName().getGivenName();
        String surname = customerDTO.getPersonName().getSurname();
        String ratePlanCode = reservationRoomStayDTO.getRatePlanCode();
        String roomTypeCode = reservationRoomStayDTO.getRoomTypeCode();
        int adults = reservationRoomStayDTO.getAdultCount();
        int children = reservationRoomStayDTO.getChildCount();
        int numberOfUnit = reservationRoomStayDTO.getNumberOfUnits();

        ReservationItem reservationItem = new ReservationItem();
        reservationItem.setErsp(erspNo);
        reservationItem.setGivenName(givenName);
        reservationItem.setSurename(surname);
        reservationItem.setStart(DateTimeUtils.formatDate(stayDateRange.getCheckInDate()));
        reservationItem.setDuration(stayDateRange.lengthOfStay());
        reservationItem.setRatePlanCode(ratePlanCode);
        reservationItem.setRoomTypeCode(roomTypeCode);
        reservationItem.setConfirmNumber(confirmNumber);
        reservationItem.setAdults(adults);
        reservationItem.setChildren(children);
        reservationItem.setNumberOfUnit(numberOfUnit);

        return reservationItem;
    }

    private ReservationModifyItem createModifyItem(ReservationRoomStayDTO reservationRoomStayDTO, String erspNo, String confirmNumber) {
        StayDateRangeDTO stayDateRange = reservationRoomStayDTO.getStayDateRange();
        com.derbysoft.remote.dswitch.generic.dto.CustomerDTO customerDTO = reservationRoomStayDTO.getGuests().get(0);
        String givenName = customerDTO.getPersonName().getGivenName();
        String surname = customerDTO.getPersonName().getSurname();
        String ratePlanCode = reservationRoomStayDTO.getRatePlanCode();
        String roomTypeCode = reservationRoomStayDTO.getRoomTypeCode();
        int adults = reservationRoomStayDTO.getAdultCount();
        int children = reservationRoomStayDTO.getChildCount();
        int numberOfUnit = reservationRoomStayDTO.getNumberOfUnits();

        ReservationModifyItem reservationModifyItem = new ReservationModifyItem();
        reservationModifyItem.setErsp(erspNo);
        reservationModifyItem.setGivenName(givenName);
        reservationModifyItem.setSurename(surname);
        reservationModifyItem.setStart(DateTimeUtils.formatDate(stayDateRange.getCheckInDate()));
        reservationModifyItem.setDuration(stayDateRange.lengthOfStay());
        reservationModifyItem.setRatePlanCode(ratePlanCode);
        reservationModifyItem.setRoomTypeCode(roomTypeCode);
        reservationModifyItem.setConfirmNumber(confirmNumber);
        reservationModifyItem.setAdults(adults);
        reservationModifyItem.setChildren(children);
        reservationModifyItem.setNumberOfUnit(numberOfUnit);

        return reservationModifyItem;
    }

    private void saveOrUpdateReservation(TransactionActionType actionType, String erspNo, String hotelCode) {
        if (actionType == TransactionActionType.MODIFY) {
            reservationRepository.updateStatus(erspNo, Status.MODIFIED);
        } else {
            reservationRepository.create(erspNo, hotelCode, Status.CONFIRMED.name());
        }
    }

    private GenericRS<HotelReservationRS> noAllotment(GenericRQ<HotelReservationRQ> request,
                                                      OTAHotelAvailRS otaHotelAvailRS, String erspNo) {
        updateStatus(request.getPayload().getResActionType(), erspNo, false);
        ResponseHeader responseHeader = new ResponseHeader();
        responseHeader.setTaskId(request.getHeader().getTaskId());
        ReservationRoomStayDTO roomStayDTO = request.getPayload().getHotelReservation().getRoomStays().get(0);
        return createNoAllotmentResponse(otaHotelAvailRS, responseHeader, roomStayDTO);
    }

    private void updateStatus(TransactionActionType actionType, String ersp, boolean success) {
        if (actionType == TransactionActionType.COMMIT) {
            reservationRepository.updateStatus(ersp, success ? Status.CONFIRMED : Status.BOOK_FAILED);
        } else if (actionType == TransactionActionType.MODIFY) {
            reservationRepository.updateStatus(ersp, success ? Status.MODIFIED : Status.MODIFY_FAILED);
        }
    }

    private void setConfirmNumber(GenericRS<HotelReservationRS> reservationRS, OTAHotelResRS otaHotelResRS) {
        String hotelConfirmNumber = getConfirmNumber(otaHotelResRS);
        HotelReservationIDDTO hotelReservationIDDTO = new HotelReservationIDDTO();
        hotelReservationIDDTO.setResIDType(UniqueIDType.HOTEL);
        hotelReservationIDDTO.setResIDValue(hotelConfirmNumber);
        reservationRS.getPayload().getHotelReservationIDs().add(hotelReservationIDDTO);
    }

    private String getConfirmNumber(OTAHotelResRS otaHotelResRS) {
        String hotelConfirmNumber = otaHotelResRS.getHotelReservations().getHotelReservation().get(0)
                .getResGlobalInfo().getHotelReservationIDs().getHotelReservationID().get(0).getResIDValue();
        Assert.notNull(hotelConfirmNumber, "no hotel reservation id supplied");
        return hotelConfirmNumber;
    }

    private GenericRS<HotelReservationRS> createFailedResponse(OTAHotelResRS otaHotelResRS,
                                                               GenericRQ<HotelReservationRQ> request) {
        GenericRS<HotelReservationRS> response = new GenericRS<HotelReservationRS>();
        ErrorsDTO errors = new ErrorsDTO();
        ErrorDTO errorDTO = new ErrorDTO();
        errorDTO.setSource(provider);
        errorDTO.setCode(otaHotelResRS.getErrors().getError().get(0).getCode() == null ?
                SYSTEM : otaHotelResRS.getErrors().getError().get(0).getCode());
        StringBuilder message = new StringBuilder();
        for (Error error : otaHotelResRS.getErrors().getError()) {
            message.append(error.getShortText()).append(" ");
        }
        errorDTO.setMessage(message.toString());
        errors.getErrorList().add(errorDTO);
        response.setErrors(errors);
        ResponseHeader responseHeader = new ResponseHeader();
        responseHeader.setTaskId(request.getHeader().getTaskId());
        response.setHeader(responseHeader);
        return response;
    }

    private OTAHotelAvailRS getAvailAbility(GenericRQ<HotelReservationRQ> request,
                                            ChannelAuthorization authorization) {
        OTAHotelAvailRQ otaHotelAvailRQ = hotelPreviewRQTranslator.translate(request, null);
        return hotelAvailWebService.invoke(otaHotelAvailRQ, true,
                AccountProvider.of(EnvironmentUtils.getGlobalIhgAvailabilityUrl(), authorization));
    }
}